ViewPager和Fragment一篇就够了 您所在的位置:网站首页 viewpager get selected fragment ViewPager和Fragment一篇就够了

ViewPager和Fragment一篇就够了

2023-09-15 20:08| 来源: 网络整理| 查看: 265

ViewPager显示多Fragment使用问题

前言:每当使用ViewPager时,对于选用什么适配器,缓存多少页面,是否需要懒加载以及Fragment的数据刷新经常会有些疑问,网络上的答案很多,但是很少有一篇能够对一些疑问进行总结,本文主要在于记录,方便日后查看。

1.FragmentPagerAdapter和FragmentPagerStateAdapter的区别,使用场景

setOffScreenPageLimit(int limit)设置viewpager左右预加载页

区别:

FragmentPagerAdapter将每一个生成的Fragment保存在内存中,limit外Fragment没有销毁,生命周期为onPause->onStop->onDestroyView,onCreateView->onStart->onResume,但Fragment的成员变量都没有变,所以可以缓存根View,避免重复inflate。

FragmentPageAdater下Fragment的生命周期.png private View mRootView; @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { Log.e(TAG, "onCreateView: page_" + mPosition); if (mRootView == null) { mRootView = inflater.inflate(R.layout.fragment_test, container, false); initView(mRootView); } return mRootView; }

FragmentStatePagerAdapter对limit外的Fragment销毁,生命周期为onPause->onStop->onDestoryView->onDestory->onDetach, onAttach->onCreate->onCreateView->onStart->onResume。

FragmentStatePageAdapter下Fragment的生命周期.png

使用场景:对于需要缓存在内存中的固定较少数量的静态页面使用FragmentPagerAdapter,如引导页,Tab页面;对于拥有大量页面的情况应使用FragmentStatePagerAdapter避免占用大量内存,如图片预览

2.是否有必要在适配器的public Fragment getItem(int position)方法中返回缓存List中的Fragment

对于FragmentPagerAdapter,instantiateItem()先从FragmentManager.findFragmentByTag()中查找FragmentManager中List缓存的Fragment,取不到则会调用getItem(),所以对于缓存在内存中的FragmentPagerAdapter没有必要再使用一个List缓存Fragment,因为FragmentPagerAdapter会缓存每一个加载过的Fragment到内存中。

instantiateItem.png makeFragmentName.png

对于FragmentStatePagerAdapter的instantiateItem()则会缓存limit左右的Fragment,超过limit则会回收,当Fragment没有缓存时重新调用getItem(),因为页面比较多,所以也没必要使用List缓存Fragment占用内存,否则FragmentStatePagerAdapter没有意义。

instantiateItem.png fragments.png 3.ViewPager为什么要懒加载,什么情况适用?

ViewPager的setOffScreenPageLimit()方法默认limit为1,既会预加载左右页面,而为了节省流量,理想情况是当用户切换到该界面时才会调用网络请求获取数据。相关方法为setUserVisibleHint(),当前页面为true,预加载页面为false,只有Fragment从可见到不可见或者从不可见到可见时会调用,Fragment初次创建时setUserVisibleHint先于onCreateView()调用,所以可以由此判断Fragment是否初始创建。

ViewPager首次显示的页面经过方法调用setUserVisibleHint(false)->setUserVisibleHint(true)->onCreateView()...,所以该页面的数据加载放在onCreateView中;其它预加载页面预加载时setUserVisibleHint(false)->onCreateView()...,当选中该页面显示时调用setUserVisibleHint(true),所以预加载页面数据加载放在setUserVisibleHint中。

懒加载limit内Fragment的生命周期.png /** * 延迟加载Fragment * Created by flying on 2017/3/2. */ public abstract class LazyLoadFragment extends BaseFragment { protected boolean bIsViewCreated; protected boolean bIsDataLoaded; @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(getLayoutResId(), container, false); initView(view); bIsViewCreated = true; if (getUserVisibleHint() && !bIsDataLoaded) { loadData(); bIsDataLoaded = true; } return view; } @Override public void onDestroyView() { super.onDestroyView(); bIsViewCreated = false; bIsDataLoaded = false; } @Override public void setUserVisibleHint(boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); if (isVisibleToUser && bIsViewCreated && !bIsDataLoaded) { loadData(); bIsDataLoaded = true; } } /** * @return 布局资源id */ protected abstract int getLayoutResId(); /** * 初始化View */ protected abstract void initView(View view); /** * 加载数据 */ protected abstract void loadData(); }

因为懒加载需要设置setOffScreenPageLimit,所以适合有网络请求、页面较少且需要缓存的Tab页面,配合FragmentPagerAdapter使用,因为limit要包括所有的界面,在limit内FragmentStatePagerAdapter和FragmentPagerAdapter没有区别。

4.ViewPager刷新数据

一般使用PagerAdapter的notifyDataSetChanged方法来刷新数据,但是很多时候数据没有更新,先来看PagerAdapter的notifyDataSetChanged方法

notifyDataSetChanged.png

观察者模式:

观察者模式.png nofifyChanged.png onChanged.png

ViewPager中的PagerObserver实现了DateSetObserver

ViewPager_PagerObserver.png

ViewPager中的dataSetChanged方法会根据adapter.getItemPosition返回的值来判断是否DestroyItem

dataSetChanged.png getItemPosition默认会返回POSITION_UNCHANGED,而ViewPager中dataSetChanged只有当返回POSITION_NONE时才会销毁页面重新创建 getItemPosition.png 继续看ViewPager中dataSetChanged方法 needPopulate.png setCurrentItemInternal.png

接着到populate方法

populate.png 终于跑到adapter的instantiateItem方法了 addNewItem.png FragmentPagerAdapter的inistantiateItem和destoryItem方法.png FragmentStatePagerAdapter的destoryItem方法.png

所以如果想通过adapter.notifyDataSetChanged来刷新页面时,必须继承FragmentStatePagerAdapter,因为FragmentPagerAdapter会缓存Fragment,不会走getItem方法,同时将所要刷新页面的getItemPosition返回POSITION_NONE

@Override public int getItemPosition(Object object) { return POSITION_NONE; }

还有其他的一种做法,拿到Fragment,通过Fragment中的public方法来刷新页面,由FragmentPagerAdapter的instantiateItem方法内部通过tag查找Fragment,因此可以保存其相同的tag

private SparseArray mTags = new SparseArray(); @Override public Object instantiateItem(ViewGroup container, int position) { mTags.put(position, makeFragmentName(container.getId(), position)); return super.instantiateItem(container, position); } @Override public void destroyItem(ViewGroup container, int position, Object object) { mTags.remove(position); super.destroyItem(container, position, object); } private String makeFragmentName(int viewId, int position) { return "android:switcher:" + viewId + ":" + position; }

然后获取Fragment

Fragment fragment = getSupportFragmentManager().findFragmentByTag(mTags.get(position)); fragment.XXX();

由第二节FragmentStatePagerAdapter的instantiateItem方法可知,其保存时没有对Fragment添加tag,ViewPager中的Fragment也不能指定id,只有通过调用

Fragment fragment = (Fragment)(fragmentStatePagerAdapter.instantiateItem(viewpager, position));

来获取Fragment

参考资料 1.ViewPager ,PagerAdapter,FragmentPagerAdapter,FragmentStatePagerAdapter 2.如何高效的使用ViewPager,以及FragmentPagerAdapter与FragmentStatePagerAdapter的区别 3.FragmentPagerAdapter与FragmentStatePagerAdapter区别 4.死磕Fragment生命周期 5.ViewPager刷新问题详解 总结

有Tab时:需要设置setOffScreenPageLimit,FragmentPageAdapter和FragmentStatePageAdapter效果相同,让Fragment都缓存在内存中,否则Fragment销毁了再次点击Tab选中又会重新创建会很突兀。需要网络请求时则执行延迟加载策略,无需网络请求时可以正常创建Fragment。

无Tab时:无需设置SetOffScreenPageLimit,因为默认limit是1,会预加载左右界面,不会显得突兀。页面较多时则选用占用内存少的FragmentStatePageAdapter,如浏览大图页面;页面较少时则选用加载到内存的FragmentPageAdapter, 如引导页,需要注意的是FragmentPageAdapter在limit外的Fragment没有销毁,生命周期为onPause->onStop->onDestroyView, onCreateView->onStart->onResume,但Fragment的成员变量都没有变,所以可以缓存根View。

如果需要刷新所有limit内的页面,继承FragmentStatePagerAdapter, 设置getItemPosition返回POSITION_NONE,再调用notifyDataSetChanged;如果只需要刷新单个页面,则通过获取Fragment的引用,再通过public方法来更新数据。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有